package ru.geyes.screw.collections;

import ru.geyes.screw.closures.Function1B;
import ru.geyes.screw.closures.Function1;
import ru.geyes.screw.closures.Function1V;
import ru.geyes.screw.tuples.Tuple2;

import java.util.List;

/**
 * @author alex
 *         date 17.05.2008
 *         time 21:41:51
 */
public abstract class Option<T> implements OptionX<T> {
    public static class Some<T> extends Option<T> {
        private T x;

        public Some(T x) {
            this.x = x;
        }

        public List<T> getList() {
            return ListXUtil.wrapToList(x);
        }

        public Option<T> getFirst() {
            return Option.some(x);
        }

        public Option<T> getLast() {
            return Option.some(x);
        }

        public ListX<T> filter(Function1B filterF) {
            if (filterF.apply(x))
                return ListXUtil.wrap(x);
            else
                return ListXUtil.emptyListX();
        }

        public <F> ListX<F> map(Function1<F, T> mapperF) {
            return ListXUtil.wrap(mapperF.apply(x));
        }

        public <F> MapX<F, T> map2Map(Function1<Tuple2<F, T>, T> mapperF) {
            return new HashMapX<F,T>(mapperF.apply(x));
        }

        public T get() {
            return x;
        }

        public T getOrElse(T other) {
            return x;
        }

        public T getOrThrow(Throwable e) {
            return x;
        }

        public boolean isDefined() {
            return true;
        }

        public void foreach(Function1V<T> f) {
            f.apply(x);
        }

        public ListX<T> cloneList() {
            return ListXUtil.wrap(x);
        }

        public ListX<T> append(T o) {
            return ListXUtil.wrap(x).append(o);
        }

        public ListX<T> insert(T o, int pos) {
            return ListXUtil.wrap(x).insert(o, pos);
        }

        public ListX<T> push(T o) {
            return ListXUtil.wrap(x).push(o);
        }

        public ListX<T> appendUnmod(T o) {
            return ListXUtil.wrap(x).appendUnmod(o);
        }

        public ListX<T> insertUnmod(T o, int pos) {
            return ListXUtil.wrap(x).insert(o, pos);
        }

        public ListX<T> pushUnmod(T o) {
            return ListXUtil.wrap(x).pushUnmod(o);
        }

        public Option<T> clone() {
            return Option.some(x);
        }
    }

    public static class None<T> extends Option<T> {

        public List<T> getList() {
            return ListXUtil.emptyList();
        }

        public Option<T> getFirst() {
            return null;
        }

        public Option<T> getLast() {
            return null;
        }

        public ListX<T> filter(Function1B filterF) {
            return ListXUtil.emptyListX();
        }

        public <F> ListX<F> map(Function1<F, T> mapperF) {
            return ListXUtil.emptyListX();
        }

        public <F> MapX<F, T> map2Map(Function1<Tuple2<F, T>, T> mapperF) {
            return MapXUtils.emptyMapX();
        }

        public void foreach(Function1V<T> f) {
            //nothing
        }

        public ListX<T> cloneList() {
            return ListXUtil.emptyListX();
        }

        public ListX<T> append(T o) {
            return ListXUtil.wrap(o);
        }

        public ListX<T> insert(T o, int pos) {
            return new ArrayListX<T>().insert(o, pos);
        }

        public ListX<T> push(T o) {
            return ListXUtil.wrap(o);
        }

        public ListX<T> appendUnmod(T o) {
            return ListXUtil.wrap(o);
        }

        public ListX<T> insertUnmod(T o, int pos) {
            return new ArrayListX<T>().insert(o, pos);
        }

        public ListX<T> pushUnmod(T o) {
            return ListXUtil.wrap(o);
        }

        public Option<T> clone() {
            return Option.none();
        }

        public T get() {
            return null;
        }

        public T getOrElse(T other) {
            return other;
        }

        public T getOrThrow(Throwable e) throws Throwable {
            throw e;
        }

        public boolean isDefined() {
            return false;
        }
    }

    public static <T> Option<T> some(T x) {
        return new Some<T>(x);
    }

    public static <T> Option<T> none() {
        return new None<T>();
    }

    public static <T> Option<T> option(T o) {
        if (o != null)
            return new Some(o);
        else
            return new None();
    }

}
